'''
Created on 2011-03-27

@author: t-bone
'''
from google.appengine.api import urlfetch, taskqueue
from django.http import HttpResponse
from django.utils import simplejson as json
from datastore.models import User
import Cookie
import base64
import cgi
import settings
import datetime
import hashlib
import hmac
import logging
import time
import traceback
import urllib
from uuid import uuid4

_USER_FIELDS = u'name,email,picture,friends,photos'

class FacebookHandler():

    csrf_protect = True
    
    def __init__(self,request):
        
        self.request = request
        """Sets up the request specific Facebook and User instance"""
        facebook = Facebook()
        user = None
    
        # initial facebook request comes in as a POST with a signed_request
        if u'signed_request' in self.request.POST:
            facebook.load_signed_request(self.request.POST['signed_request'])
            # we reset the method to GET because a request from facebook with a
            # signed_request uses POST for security reasons, despite it
            # actually being a GET. in webapp causes loss of request.POST data.
            self.request.method = u'GET'
            #self.set_cookie(
                #'u', facebook.user_cookie, datetime.timedelta(minutes=1440))
        elif 'u' in self.request.COOKIES:
            facebook.load_signed_request(self.request.COOKIES.get('u'))

        # try to load or create a user object
        if facebook.user_id:
            user = User.get_by_key_name(facebook.user_id)
            if user:
                # update stored access_token
                if facebook.access_token and \
                        facebook.access_token != user.access_token:
                    user.access_token = facebook.access_token
                    user.put()
                # refresh data if we failed in doing so after a realtime ping
                if user.dirty:
                    user.refresh_data()
                # restore stored access_token if necessary
                if not facebook.access_token:
                    facebook.access_token = user.access_token
    
            if not user and facebook.access_token:
                me = facebook.api(u'/me', {u'fields': _USER_FIELDS})
                try:
                    friends = [user[u'id'] for user in me[u'friends'][u'data']]
                    user = User(key_name=facebook.user_id,
                        user_id=facebook.user_id, friends=friends,
                        access_token=facebook.access_token, name=me[u'name'],
                        email=me.get(u'email'), picture=me[u'picture'])
                    user.put()
                except KeyError, ex:
                    pass # ignore if can't get the minimum fields
                
        """Issue and handle CSRF token as necessary"""
        self.csrf_token = self.request.COOKIES.get(u'c')
        #=======================================================================
        # if not self.csrf_token:
        #    self.csrf_token = str(uuid4())[:8]
        #    self.set_cookie('c', self.csrf_token)
        #=======================================================================
        if self.request.method == u'POST' and self.csrf_protect and \
                self.csrf_token != self.request.POST.get(u'_csrf_token'):
            raise CsrfException(u'Missing or invalid CSRF token.')
    
        self.facebook = facebook
        self.user = user
        
    def set_data(self,data = None):
        
        if not(data):
            self.data = {}
        else:
            self.data = data
        self.data[u'js_conf'] = json.dumps({
        u'appId': settings.FACEBOOK_APP_ID,
        u'canvasName': settings.FACEBOOK_CANVAS_NAME,
        u'userIdOnServer': self.user.user_id if self.user else None,
        })
        self.data[u'logged_in_user'] = self.user
        self.data[u'message'] = self.get_message()
        self.data[u'csrf_token'] = self.csrf_token
        self.data[u'canvas_name'] = settings.FACEBOOK_CANVAS_NAME

    def set_message(self, **obj):
        """Simple message support"""
        self.set_cookie('m', base64.b64encode(json.dumps(obj)) if obj else None)

    def get_message(self):
        """Get and clear the current message"""
        message = self.request.COOKIES.get(u'm')
        if message:
            self.set_message()  # clear the current cookie
            return json.loads(base64.b64decode(message))

    def handle_exception(self, ex, debug_mode):
        """Invoked for unhandled exceptions by webapp"""
        self.log_exception(ex)
        self.render(u'error',
            trace=traceback.format_exc(), debug_mode=debug_mode)



class FacebookResponse(HttpResponse):

    def init_facebook(self,facebook):

        if u'signed_request' in facebook.request.POST:
            self.set_cookie(
                'u', facebook.facebook.user_cookie, datetime.timedelta(minutes=1440))

        if not facebook.csrf_token:
            facebook.csrf_token = str(uuid4())[:8]
            self.set_cookie('c', facebook.csrf_token)
        
    def set_cookie(self, name, value, expires=None):
        """Set a cookie"""
        if value is None:
            value = 'deleted'
            expires = datetime.timedelta(minutes=-50000)
        jar = Cookie.SimpleCookie()
        jar[name] = value
        jar[name]['path'] = u'/'
        if expires:
            if isinstance(expires, datetime.timedelta):
                expires = datetime.datetime.now() + expires
            if isinstance(expires, datetime.datetime):
                expires = expires.strftime('%a, %d %b %Y %H:%M:%S')
            jar[name]['expires'] = expires
        self.__setitem__(*jar.output().split(u': ', 1))    

class CsrfException(Exception):
    pass

class FacebookApiError(Exception):
    def __init__(self, result):
        self.result = result

    def __str__(self):
        return self.__class__.__name__ + ': ' + json.dumps(self.result)

class Facebook(object):
    """Wraps the Facebook specific logic"""
    def __init__(self, app_id=settings.FACEBOOK_APP_ID,
            app_secret=settings.FACEBOOK_APP_SECRET):
        self.app_id = app_id
        self.app_secret = app_secret
        self.user_id = None
        self.access_token = None
        self.signed_request = {}

    def api(self, path, params=None, method=u'GET', domain=u'graph',connection_type = ''):
        """Make API calls"""
        if not params:
            params = {}
        params[u'method'] = method
        if u'access_token' not in params and self.access_token:
            params[u'access_token'] = self.access_token
            
        result = urlfetch.fetch(url=u'https://' + domain + u'.facebook.com' + path + connection_type, 
                              payload=urllib.urlencode(params),
                              method=urlfetch.POST,
                              allow_truncated=False,
                              headers={u'Content-Type': u'application/x-www-form-urlencoded'}).content
        result = json.loads(result)
        if isinstance(result, dict) and u'error' in result:
            raise FacebookApiError(result)
        return result
 
    def fql(self, query, params=None, domain=u'api'):
        """Make API calls"""
        if not params:
            params = {}
        if u'access_token' not in params and self.access_token:
            params[u'access_token'] = self.access_token
        
        params[u'format']='JSON'
        params[u'query']=query
        result = urlfetch.fetch(url=u'https://' + domain + u'.facebook.com/method/fql.query?query=QUERY', 
                              payload=urllib.urlencode(params),
                              method=urlfetch.POST,
                              allow_truncated=False,
                              headers={u'Content-Type': u'application/x-www-form-urlencoded'}).content
        result = json.loads(result)
        if isinstance(result, dict) and u'error' in result:
            raise FacebookApiError(result)
        return result   
    
    def load_signed_request(self, signed_request):
        """Load the user state from a signed_request value"""
        try:
            sig, payload = signed_request.split(u'.', 1)
            sig = self.base64_url_decode(sig)
            data = json.loads(self.base64_url_decode(payload))

            expected_sig = hmac.new(
                self.app_secret, msg=payload, digestmod=hashlib.sha256).digest()

            # allow the signed_request to function for upto 1 day
            if sig == expected_sig and \
                    data[u'issued_at'] > (time.time() - 86400):
                self.signed_request = data
                self.user_id = data.get(u'user_id')
                self.access_token = data.get(u'oauth_token')
        except ValueError, ex:
            pass # ignore if can't split on dot

    @property
    def user_cookie(self):
        """Generate a signed_request value based on current state"""
        if not self.user_id:
            return
        payload = self.base64_url_encode(json.dumps({
            u'user_id': self.user_id,
            u'issued_at': str(int(time.time())),
        }))
        sig = self.base64_url_encode(hmac.new(
            self.app_secret, msg=payload, digestmod=hashlib.sha256).digest())
        return sig + '.' + payload

    @staticmethod
    def base64_url_decode(data):
        data = data.encode(u'ascii')
        data += '=' * (4 - (len(data) % 4))
        return base64.urlsafe_b64decode(data)

    @staticmethod
    def base64_url_encode(data):
        return base64.urlsafe_b64encode(data).rstrip('=')